#ifndef __LOG_H__
#define __LOG_H__
#include <stdio.h>
#include <assert.h>

#define LOG_LEVEL_DEF(f) \
    f(NONE) \
    f(ERROR) \
    f(WARN) \
    f(INFO) \
    f(DEBUG) \
    f(MAX) \

typedef enum LogLevel {
#define Func(x) LOG_##x,
    LOG_LEVEL_DEF(Func)
#undef Func
}LogLevel;

typedef struct LogDev {
    struct LogDev *prev;
    char          *name; 
    int            fd;
    LogLevel       level;
}LogDev;

extern const char *LogLevelName[];
extern LogDev *log_devs;

LogDev* _log_init_fd(int fd, const char *name, LogLevel level);
#define log_init_fd(fd, name, level) \
    _log_init_fd(fd, name, LOG_##level)

LogDev* _log_init_file(const char *file, const char *name, LogLevel level);
#define log_init_file(file, name, level) \
    _log_init_file(file, name, LOG_##level)

#define log_init_std(fp, name, level) \
    ({ \
        LogDev *dev = NULL; \
        if(fp == stdout) { \
            dev = log_init_fd(1, name, level); \
        }else if(fp == stderr) { \
            dev = log_init_fd(2, name, level); \
        }else{ \
            fprintf(stderr, "log_init_std: unknown fp\n"); \
        } \
        dev; /* return */ \
    })

void log_free(LogDev *dev);

void _log_set_level(LogDev *dev, LogLevel level);
#define log_set_level(dev, level) \
    _log_set_level(dev, LOG_##level)

LogLevel _log_get_level(LogDev *dev);
#define log_get_level(dev) \
    _log_get_level(dev)

#define log_level_name(level) \
    ({ LogLevelName[level]; })

#if !(defined(LOG_FMT) && defined(LOG_ARGS))
#undef LOG_FMT
#undef LOG_ARGS
#define LOG_FMT(name, level, fmt)   "[" level "] %s: " fmt "\n"
#define LOG_ARGS(name, level, ...)  name, ##__VA_ARGS__
#endif

#define __LOG_ARGS_WRAP(...) ,##__VA_ARGS__
#define _LOG_ARGS_WRAP(...) __LOG_ARGS_WRAP(__VA_ARGS__)
#define LOG_ARGS_WRAP(name, level, ...) _LOG_ARGS_WRAP(LOG_ARGS(name, level, ##__VA_ARGS__))

#define LOG_FMT_WRAP(name, level, fmt) LOG_FMT(name, level, fmt)

#define log(dev, _level, fmt, ...) \
    do{ \
        assert(dev != NULL); \
        if(LOG_##_level > dev->level) \
            break; \
        dprintf(dev->fd, \
                LOG_FMT_WRAP(dev->name, #_level, fmt) \
                LOG_ARGS_WRAP(dev->name, #_level, ##__VA_ARGS__)); \
    }while(0)

#endif
